using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace ClassifierLib.Classifier.Filters
{
    public class Update : RuleFilter
    {
        private double _reward = 1000;
        private double _alpha = 0.1;
        private double _beta = 0.15;
        private double _rewardGiven = 1000;
        private double _nu = 2;

        [CategoryAttribute("Settings"), Browsable(true), ReadOnly(false), BindableAttribute(false), DesignOnly(false), DescriptionAttribute("The reward given to matching rules")]
        public double RewardGiven
        {
            get
            {
                return _rewardGiven;
            }
            set
            {
                _rewardGiven = value;
                OnPropertyChanged("RewardGiven");
            }
        }

        public double RewardReceived
        {
            get { return _reward; }
        }

        //TODO: Rename these so as not to confuse the action with the value -   BPH EDIT 091204
        private bool _rewardIsGiven;
        /// <summary>
        /// Boolean bit indicating that a reward is given in this update
        /// </summary>
        public bool RewardIsGiven
        {
            get { return _rewardIsGiven; }
        }

        [CategoryAttribute("Settings"), Browsable(true), ReadOnly(false), BindableAttribute(false), DesignOnly(false), DescriptionAttribute("A parameter")]
        public double Alpha
        {
            get
            {
                return _alpha;
            }
            set
            {
                _alpha = value;
                OnPropertyChanged("Alpha");
            }
        }

        [CategoryAttribute("Settings"), Browsable(true), ReadOnly(false), BindableAttribute(false), DesignOnly(false), DescriptionAttribute("A parameter")]
        public double Beta
        {
            get
            {
                return _beta;
            }
            set
            {
                _beta = value;
                OnPropertyChanged("Beta");
            }
        }

        //accuracy scaling factor
        [CategoryAttribute("Settings"), Browsable(true), ReadOnly(false), BindableAttribute(false), DesignOnly(false), DescriptionAttribute("Accuracy scaling factor")]
        public double Nu
        {
            get
            {
                return _nu;
            }
            set
            {
                _nu = value;
                OnPropertyChanged("Nu");
            }
        }

        public Update()
        {
            this.Name = "Update";
            if (!RegisteredTypes.Contains(GetType()))
                RegisteredTypes.Add(GetType());
        }

        //DEBUG
        private bool _exploiting;

        //DEBUG - Need to find better way
        public bool Exploiting
        {
            set { _exploiting = value; }
        }

        public override Population Apply(Population Population, Population ActionSet, Environment.State State, Action Action)
        {
            if (ActionSet.Count > 0)
            {
                //TODO: rephrase this to avoid [] index on PopSet
                var list = ActionSet.ToList();

                CalculateReward(list[0].Action, Action);
                if (!_exploiting)
                    UpdateSelected(ActionSet);
            }

            // last filter, so doesnt need to provide valid input to next filter
            return null;
        }

        private void CalculateReward(Action Action, Action DesiredAction)
        {
            if (Action.Value == DesiredAction.Value)        //BPH - Changed to compare .Value as opposed to instance of Action
            {
                _reward = RewardGiven;
                _rewardIsGiven = true;
            }
            else
            {
                _reward = 0;
                _rewardIsGiven = false;
            }
        }

        /// <summary>
        /// Update selected rules based on calculated reward
        /// (Similar to updateASet from old code)
        /// </summary>
        /// <param name="PopSet"></param>
        private void UpdateSelected(Population ActionSet)
        {

            // find total selected population size
            double actionSize = 0;                  //Formerly populationSize BPH
            double totalAccuracy = 0;
            foreach (Rule r in ActionSet)
            {
                actionSize += r.Numerosity;
            }

            foreach (Rule r in ActionSet)
            {
                r.Statistics["age"]++;

                
                if (r.Statistics["age"] < (1.0 / _beta))
                {

                    r.Statistics["nicheSize"] += (actionSize - r.Statistics["nicheSize"]) / (r.Statistics["age"]);                      //do not apply beta to nichesize under certain age
                    r.Statistics["prediction"] += (_reward - r.Statistics["prediction"]) / (r.Statistics["age"]);       //BPH equivalent to sum of all rewards divided by age
                    r.Statistics["error"] += ((Math.Abs(_reward - r.Statistics["prediction"])) - r.Statistics["error"]) / (r.Statistics["age"]);  //BPH
                }
                else
                {

                    r.Statistics["nicheSize"] += _beta * (actionSize - r.Statistics["nicheSize"]);    //No need for ceiling command - just average
                    r.Statistics["prediction"] += _beta * (_reward - r.Statistics["prediction"]);       //BPH
                    r.Statistics["error"] += _beta * (Math.Abs(_reward - r.Statistics["prediction"]) - r.Statistics["error"]);  //BPH
                }

                double error = r.Statistics["error"];
                double org_error = r.Statistics["org_error"];
                if (error > org_error)
                {
                    r.Statistics["accuracy"] = _alpha * Math.Pow(org_error / error, _nu);
                }
                else
                    r.Statistics["accuracy"] = 1;


                //summation of total rule accuracy to be used in relative accuracy
                totalAccuracy += r.Statistics["accuracy"] * r.Numerosity;


                //TODO: is this intentional code, check with WB
                if (double.IsNaN(r.Statistics["fitness"]))
                    r.Statistics["fitness"] = 0;

                //if (double.IsInfinity(r.Statistics["fitness"]) || (double.IsInfinity(r.Statistics["error"])) || (double.IsInfinity(r.Statistics["prediction"])) || double.IsNaN(r.Statistics["fitness"]) || (double.IsNaN(r.Statistics["error"])) || (double.IsNaN(r.Statistics["prediction"])))
                //    r.Statistics["age"]++;

            }

            //update fitness
            foreach (Rule r in ActionSet)
            {
                if (r.Statistics["age"] < (1.0 / _beta))
                {
                    r.Statistics["fitness"] += ((r.Statistics["accuracy"] * r.Numerosity / totalAccuracy) - r.Statistics["fitness"]) / r.Statistics["age"];
                }
                else
                {
                    r.Statistics["fitness"] += _beta * ((r.Statistics["accuracy"] * r.Numerosity / totalAccuracy) - r.Statistics["fitness"]);
                }
            }
        }
    }
}
